/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package filecopier.gui.restore;

import java.io.File;
import java.util.List;
import java.io.IOException;
import java.beans.PropertyChangeSupport;
import java.beans.PropertyChangeListener;
import java.beans.PropertyChangeEvent;
import javax.swing.JTextArea;
import javax.swing.SwingWorker;
import javax.swing.JOptionPane;
import filecopier.Singleton;
import filecopier.ActionCommand;
import filecopier.ActionCancelException;
import filecopier.restore.FileRestoreCancelException;
import filecopier.gui.ProgressBarPanel;
import filecopier.gui.WorkerMessage;
import filecopier.script.Script;
import filecopier.script.ScriptEntry;
import filecopier.script.RestoreScript;
import filecopier.restore.AbstractRestoreWorker;
import org.apache.log4j.Logger;
import org.apache.log4j.Level;
import custom.xml.DOMHelperException;

/**
 *
 * @author david
 */
public class RestoreGuiWorker extends AbstractRestoreWorker implements PropertyChangeListener, ActionCommand {

    private Logger logger = null;
    private ProgressBarPanel progressBarPanel = null;
    private List<ScriptEntry> scriptEntries = null;
    private PropertyChangeSupport pcs = null;
    private volatile Boolean cancelRequest = false;
    private int confirmRestoreValue = PROMPT_INIT_VALUE;
    private int restoredFileCt = 0;
    private static final int PROMPT_INIT_VALUE = -2;
    private static final int YES_OPTION = 0;
    private static final int YESTOALL_OPTION = 1;
    private static final int NO_OPTION = 2;
    private static final int NOTOALL_OPTION = 3;

    public RestoreGuiWorker(final Script restoreScript) {
        super(restoreScript);
        scriptEntries = ((RestoreScript) restoreScript).getScriptEntries();
        pcs = new PropertyChangeSupport(this);
    }

    public PropertyChangeSupport getPropertyChangeSupport() {
        return pcs;
    }

    public int getFileCt() {
        return scriptEntries != null ? scriptEntries.size() : 0;
    }

    private void setAttemptFileCt(int attemptFileCt) {
        pcs.firePropertyChange("attemptFileCt", attemptFileCt - 1, attemptFileCt);
    }

    private void setRestoreScriptDeleted(final File restoreScriptFile) {
        pcs.firePropertyChange("restoreScriptDeleted", null, restoreScriptFile);
    }

    private void setConfirmRestorePrompt(final File restoreFile, final File deleteFile) {
        pcs.firePropertyChange("confirmRestorePrompt", null, "Are you sure you want to restore " + restoreFile + " to " + deleteFile + "?");
    }

    public void propertyChange(PropertyChangeEvent pce) {
        if (pce.getPropertyName().equals("cancel")) {
            synchronized (cancelRequest) {
                cancelRequest = true;
            }
        } else if (pce.getPropertyName().equals("confirmRestorePrompt")) {
            confirmRestore((String) pce.getNewValue());
        }
    }

    private void confirmRestore(final String msg) {
        Object[] possibleValues = {"Yes", "Yes To All", "No", "No To All"};
        int val = JOptionPane.showOptionDialog(Singleton.getInstance().getFrame(), msg, "Confirm Restore",
                JOptionPane.YES_NO_CANCEL_OPTION, JOptionPane.QUESTION_MESSAGE,
                null, possibleValues, possibleValues[0]);

        pcs.firePropertyChange("confirmRestoreValue", null, val);
    }

    public void doAction() throws ActionCancelException {
        restoreFiles();
    }

    public void restoreFiles() throws ActionCancelException {
        progressBarPanel = new ProgressBarPanel("Restoring files...");
        logger = getLogger(progressBarPanel.getTextArea());
        logger = Singleton.getInstance().getFileGuiLogger();

        RestoreWorker restoreWorker = new RestoreWorker();

        // Now, add the copyWorker's FileCopyHelper as a PropertyChangeListener to the ProgressBarPanel. It needs to listen
        // for if the Cancel Button is pressed.
        progressBarPanel.addPropertyChangeListener("cancel", this);


        restoreWorker.execute();
    }

    protected boolean proceed(final File restoreFile, final File deleteFile) throws FileRestoreCancelException {
        boolean restore = false;

        setConfirmRestorePrompt(restoreFile, deleteFile);

        // Loop until user responds to the prompt.
        while (confirmRestoreValue == PROMPT_INIT_VALUE) {
            // Sleep for 250 milliseconds.
            try {
                Thread.sleep(250);
            } catch (InterruptedException ignore) {
            }
        }

        int restoreStatus = confirmRestoreValue;
        confirmRestoreValue = PROMPT_INIT_VALUE;

        switch (restoreStatus) {
            case YESTOALL_OPTION:
                // User said yes to all, so don't confirm subsequent restores.
                promptForRestore = false;
            // Fall through on purpose

            case YES_OPTION:
                restore = true;
                break;

            case NOTOALL_OPTION:
                throw new FileRestoreCancelException("Subsequent restore operations aborted");

            case NO_OPTION:
            // Fall through on purpose

            default: // JOptionPane.CLOSED_OPTION
                restore = false;
                break;
        }

        return restore;
    }

    private Logger getLogger(JTextArea textArea) {
        logger = Singleton.getInstance().getGuiLogger();

        org.apache.log4j.Appender textAreaAppender = logger.getAppender("JTextAreaAppender");

        if (textAreaAppender != null && textAreaAppender instanceof custom.log4j.JTextAreaAppender) {
            ((custom.log4j.JTextAreaAppender) textAreaAppender).setTextArea(textArea);
        }
        return logger;
    }

    private class RestoreWorker extends SwingWorker<Void, WorkerMessage> implements PropertyChangeListener {
        private int fileCt = 0;
        private float oneFilePercent = 0;

        public RestoreWorker() {
            fileCt = getFileCt();
            oneFilePercent = (float) 100 / fileCt;
            promptForRestore = Singleton.getInstance().getOptions().promptForRestore();
        }

        public void handleException(final IOException e) {
            // Send the error message only to the gui logger and the error message plus the stack trace to the file logger.
            Singleton.getInstance().getGuiLogger().error("IOException: " + e.getMessage());
            Singleton.getInstance().getFileLogger().error("IOException", e);
        }

        @Override
        public Void doInBackground() throws ActionCancelException {
            addPropertyChangeListeners();
            addRestoreGuiPropertyChangeListeners();
            restore();

            return null;
        }

        @Override
        protected void process(List<WorkerMessage> chunks) {
            for (WorkerMessage wm : chunks) {
                if (wm.getException() == null) {
                    wm.getLogger().log(wm.getLevel(), wm.getMessage());
                } else {
                    wm.getLogger().log(wm.getLevel(), wm.getMessage(), wm.getException());
                }
            }
        }

        @Override
        public void done() {
            firePropertyChange("summaryRpt", null, restoredFileCt + " out of " + fileCt + " files successfully restored.");
        }

        public void restore() throws ActionCancelException {
            fileCt = getFileCt();
            int attemptFileCt = 0;

            try {
                for (int i = 0; i < scriptEntries.size(); i++) {
                    ScriptEntry se = scriptEntries.get(i);
                    try {
                        File restorePath = se.getPath(ScriptEntry.PathType.Restore);
                        File deletePath = se.getPath(ScriptEntry.PathType.Delete);
                        restoreFile(restorePath, deletePath);
                        setAttemptFileCt(++attemptFileCt);
                        restoredFileCt++;
                        publish(new WorkerMessage(logger, Level.INFO, "File renamed from \"" + restorePath + "\" to \"" + deletePath + "\"."));
                        if (cancelRequest == true) {
                            throw new FileRestoreCancelException("Restore Operations Cancelled");
                        }
                    } catch (IOException e) {
                        Singleton s = Singleton.getInstance();
                        WorkerMessage fileMsg = new WorkerMessage(s.getFileLogger(), Level.WARN, "IOException", e);
                        WorkerMessage guiMsg = new WorkerMessage(s.getGuiLogger(), Level.WARN, e.getMessage());
                        publish(fileMsg, guiMsg);

                        // Remove this entry from the list of ScriptEntries. This restore entry will not be
                        // removed from the script file.
                        scriptEntries.remove(se);
                        // Decrement i since we just deleted an entry and the size of the scriptEntries list will be one less.
                        i--;
                        setAttemptFileCt(++attemptFileCt);
                    } catch (FileRestoreCancelException e) {
                        // Remove all following entries from the list of ScriptEntries.
                        // See if the current scriptEntry was already deleted. If it was, increment i.
                        if(!scriptEntries.get(i).getPath(ScriptEntry.PathType.Restore).exists()) i++;
                        for (; i < scriptEntries.size(); i++) {
                            se = scriptEntries.get(i);
                            scriptEntries.remove(se);
                        }
                        throw new ActionCancelException(e);
                    }
                }
            } finally {
                // Remove all files that were restored from the restore script. If all files were restored,
                // delete the restore script.
                if (scriptEntries.size() == fileCt) {
                    restoreScript.getScriptFile().delete();
                    String restoreScriptName = restoreScript.getScriptName();
                    publish(new WorkerMessage(logger, Level.INFO, "All files in \"" + restoreScriptName + "\" were restored. \"" +
                            restoreScriptName + "\" was deleted."));
                    setRestoreScriptDeleted(restoreScript.getScriptFile());
                } else {
                    publish(new WorkerMessage(Singleton.getInstance().getGuiLogger(), Level.INFO, "Updating restore script..."));
                    ScriptEntry[] seArray = new ScriptEntry[scriptEntries.size()];
                    restoredFileCt = scriptEntries.size();
                    System.out.println(scriptEntries.size());
                    scriptEntries.toArray(seArray);
                    restoreScript.removeScriptEntries(seArray);
                    try {
                        restoreScript.saveToDisk();
                        publish(new WorkerMessage(logger, Level.INFO, restoredFileCt + " out of " + fileCt + " files " +
                                (fileCt == 1 ? " was " : " were ") + "restored. All files that were restored were removed " +
                                "from \"" + restoreScript.getScriptName() + "\"."));
                    } catch (DOMHelperException e) {
                        Singleton s = Singleton.getInstance();
                        WorkerMessage fileMsg = new WorkerMessage(s.getFileLogger(), Level.ERROR, "RestoreScript \"" +
                                restoreScript.getScriptName() + "\" could not be updated.", e);
                        WorkerMessage guiMsg = new WorkerMessage(s.getGuiLogger(), Level.ERROR,
                                "RestoreScript \"" + restoreScript.getScriptName() + "\" could not be updated.");
                        publish(fileMsg, guiMsg);
                    }
                }
            }
        }

        private void addPropertyChangeListeners() {
            PropertyChangeSupport pcs = getPropertyChangeSupport();
            pcs.addPropertyChangeListener("progress", progressBarPanel);
            pcs.addPropertyChangeListener("state", progressBarPanel);
            pcs.addPropertyChangeListener("summaryRpt", progressBarPanel);
        }

        private void addRestoreGuiPropertyChangeListeners() {
            pcs.addPropertyChangeListener("attemptFileCt", this);
            pcs.addPropertyChangeListener("confirmRestoreValue", this);
            pcs.addPropertyChangeListener("confirmRestorePrompt", RestoreGuiWorker.this);
        }

        public void propertyChange(PropertyChangeEvent pce) {
            if (pce.getPropertyName().equals("attemptFileCt")) {
                int attemptFileCt = (Integer) pce.getNewValue();
                float progressFloat = (float) attemptFileCt * oneFilePercent;
                int progress = (int) progressFloat;
                setProgress(attemptFileCt < fileCt ? Math.min(progress, 100) : Math.max(progress, 100));
            } else if (pce.getPropertyName().equals("confirmRestoreValue")) {
                confirmRestoreValue = (Integer) pce.getNewValue();
            }
        }
    }
}
